#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include "pa_jst.h"
#include "crc-ccitt.h"


#define get_mod_addr(msg)  (*(mod_addr_t *)((uint8_t *)(msg) + JST_CMD_OFFSET_MOD_ADDR))
#define get_cmd_code(msg)  (*((uint8_t *)(msg) + JST_CMD_OFFSET_CMD_CODE))
#define get_rsp_flag(msg)  (*((uint8_t *)(msg) + JST_CMD_OFFSET_RSP_FLAG))
#define get_cmd_size(msg)  (*((uint8_t *)(msg) + JST_CMD_OFFSET_CMD_SIZE))
#define get_crc_data(msg)  (*(uint16_t *)((uint8_t *)(msg) + JST_CMD_OFFSET_CMD_DATA + get_cmd_size(msg)))

#define set_mod_addr(msg, addr)  (*(mod_addr_t *)((uint8_t *)(msg) + JST_CMD_OFFSET_MOD_ADDR) = *(addr))
#define set_cmd_code(msg, code)  (*((uint8_t *)(msg) + JST_CMD_OFFSET_CMD_CODE) = (code))
#define set_rsp_flag(msg, flag)  (*((uint8_t *)(msg) + JST_CMD_OFFSET_RSP_FLAG) = (flag))
#define set_cmd_size(msg, size)  (*((uint8_t *)(msg) + JST_CMD_OFFSET_CMD_SIZE) = (size))
#define set_crc_data(msg, data)  (*(uint16_t *)((uint8_t *)(msg) + JST_CMD_OFFSET_CMD_DATA + get_cmd_size(msg)) = (data))

uint8_t jst_lpa_powerswitch = 0;
char jst_lpa_sw_ver[64] = { 0 };

inline uint32_t swap32(uint8_t *buf)
{
	uint32_t d = buf[0];
	d <<= 8;
	d |= buf[1];
	d <<= 8;
	d |= buf[2];
	d <<= 8;
	d |= buf[3];
	return d;
}

inline uint32_t swap16(uint8_t *buf)
{
	uint32_t d = buf[0];
	d <<= 8;
	d |= buf[1];
	return d;
}

inline uint32_t swap_u16(uint16_t buf)
{
	return (buf & 0xFF) << 8 | (buf & 0xFF00) >> 8;
}

inline pa_ret_t jst_verify_start_end_flag(void *buf, int32_t len)
{
    if( len >= 2 )
    {
        uint8_t *ptr = (uint8_t *)buf;
        if( (ptr[0] == 0x7E) && (ptr[len - 1] == 0x7F) )
        {
            return pa_ret_ok;
        }
    }
    PALogError("wrong start-end flag");
    return pa_err_magic;
}

inline pa_ret_t jst_verify_mod_addr(void *buf, int32_t len, mod_addr_t *addr)
{
    if( len >= 3 )
    {
        mod_addr_t lpa_addr = get_mod_addr(buf);
        if( (addr->func_code == lpa_addr.func_code) && 
            (addr->addr_code == lpa_addr.addr_code) )
        {
            return pa_ret_ok;
        }
    }
    PALogError("wrong module address");
    return pa_err_mod_addr;
}

inline pa_ret_t jst_verify_cmd_code(void *buf, int32_t len, uint8_t cmd_code)
{
    if( len >= 4 )
    {
        if( cmd_code == get_cmd_code(buf) )
        {
            return pa_ret_ok;
        }
    }
    PALogError("wrong command code");
    return pa_err_com_code;
}

inline pa_ret_t jst_verify_cmd_size(void *buf, int32_t len)
{
    if( len >= 6 )
    {
        uint8_t cmd_size = get_cmd_size(buf);
        if( cmd_size == len - 1 - 5 - 2 -1 )
        {
            return pa_ret_ok;
        }
    }
    return pa_err_cmd_size;
}

inline pa_ret_t jst_verify_crc(void *buf, int32_t len)
{
    if( len >= 8 )
    {
        uint16_t crc_data = get_crc_data(buf);
        uint8_t *ptr = (uint8_t *)buf;
        uint16_t crc_calc = crc16_ccitt(ptr + JST_CMD_OFFSET_MOD_ADDR, 5 + get_cmd_size(buf));
        if( crc_calc == crc_data )
        {
            return pa_ret_ok;
        }
        PALogError("crc value %X isn't match with %X", crc_data, crc_calc);
    }
    return pa_err_crc;
}

pa_ret_t jst_msg_check(uint8_t *buf, uint32_t len, mod_addr_t *lpa_addr, uint32_t cmd_code)
{
    pa_ret_t ret = pa_ret_ok;

    ret = jst_verify_start_end_flag(buf, len);
    if( pa_ret_ok != ret )
    {
        return ret;
    }

    ret = jst_verify_crc(buf, len);
    if( pa_ret_ok != ret )
    {
        return ret;
    }

    ret = jst_verify_mod_addr(buf, len, lpa_addr);
    if( pa_ret_ok != ret )
    {
        return ret;
    }

    ret = jst_verify_cmd_code(buf, len, cmd_code);
    if( pa_ret_ok != ret )
    {
        return ret;
    }
            
    if( JST_RSP_FLAG_OK != get_rsp_flag(buf) )
    {
        PALogError("check rsp flag failed, %d", get_rsp_flag(buf));
        return pa_err_rsp_flag;
    }
    return pa_ret_ok;
}

pa_ret_t jst_get_lpa_status(int32_t com_fd, mod_addr_t *lpa_addr, jst_lpa_status *lpa_status)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[9] = {0x7E,0x0E,0x00,0x11,0x00,0x00,0xFB,0xBB,0x7F};
    
    set_mod_addr(com_cmd, lpa_addr);

    *(uint16_t *)(com_cmd + JST_CMD_OFFSET_CMD_DATA) = crc16_ccitt(&com_cmd[1], 5);

    debug_dump_buffer(9, com_cmd);

    nret = write(com_fd, com_cmd, 9);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            debug_dump_buffer(rsp_len, rsp_buf);

            ret = jst_msg_check(rsp_buf, rsp_len, lpa_addr, JST_CMD_GET_MOD_STATUS);
            if( pa_ret_ok != ret )
            {
                return ret;
            }

            jst_lpa_status *status = (jst_lpa_status *)(rsp_buf + JST_CMD_OFFSET_CMD_DATA);
            memcpy(lpa_status, status, sizeof(jst_lpa_status));
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t jst_get_lpa_sw_ver(int32_t com_fd, mod_addr_t *lpa_addr)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[9] = {0x7E,0x0E,0x00,0x12,0x00,0x00,0xAB,0xE2,0x7F};
    
    set_mod_addr(com_cmd, lpa_addr);

    *(uint16_t *)(com_cmd + JST_CMD_OFFSET_CMD_DATA) = crc16_ccitt(&com_cmd[1], 5);

    debug_dump_buffer(9, com_cmd);

    nret = write(com_fd, com_cmd, 9);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            debug_dump_buffer(rsp_len, rsp_buf);
            ret = jst_msg_check(rsp_buf, rsp_len, lpa_addr, JST_CMD_GET_MOD_SWVER);
            if( pa_ret_ok != ret )
            {
                return ret;
            }

            memcpy(jst_lpa_sw_ver, rsp_buf + JST_CMD_OFFSET_CMD_DATA, get_cmd_size(rsp_buf));

            PALogError("lpa software version: %s", jst_lpa_sw_ver);
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

/*
 * 0x0E,0x00 is default address for lpa module
 *      0x00 can be changed
 */
pa_ret_t jst_set_lpa_addr(int32_t com_fd, mod_addr_t *lpa_addr, uint8_t new_addr)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[11] = {0x7E,0x0E,0x00,0x10,0x00,0x02,0x0E,0x00,0xFF,0xFF,0x7F};

    set_mod_addr(com_cmd, lpa_addr);

    com_cmd[JST_CMD_OFFSET_CMD_DATA] = JST_MOD_ADDR_FUNC_LPA;
    com_cmd[JST_CMD_OFFSET_CMD_DATA + 1] = new_addr;

    *(uint16_t *)(com_cmd + JST_CMD_OFFSET_CMD_DATA + 2) = crc16_ccitt(&com_cmd[1], 5 + 2);

    nret = write(com_fd, com_cmd, 11);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            debug_dump_buffer(rsp_len, rsp_buf);

            mod_addr_t paddr = {JST_MOD_ADDR_FUNC_LPA, new_addr};

            ret = jst_msg_check(rsp_buf, rsp_len, &paddr, JST_CMD_SET_MOD_ADDR);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t jst_set_lpa_switch(int32_t com_fd, mod_addr_t *lpa_addr, int8_t on_off)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[10] = {0x7E,0x0E,0x00,0x22,0x00,0x01,0x00,0xFF,0xFF,0x7F};

    set_mod_addr(com_cmd, lpa_addr);
    com_cmd[JST_CMD_OFFSET_CMD_DATA] = on_off;
    jst_lpa_powerswitch = on_off;

    *(uint16_t *)(com_cmd + JST_CMD_OFFSET_CMD_DATA + 1) = crc16_ccitt(&com_cmd[1], 5 + 1);

    debug_dump_buffer(10, com_cmd);

    nret = write(com_fd, com_cmd, 10);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            debug_dump_buffer(rsp_len, rsp_buf);
            ret = jst_msg_check(rsp_buf, rsp_len, lpa_addr, JST_CMD_SET_SWITCH);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t jst_set_lpa_att(int32_t com_fd, mod_addr_t *lpa_addr, int8_t ul_att, int8_t dl_att)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[11] = {0x7E,0x0E,0x00,0x20,0x00,0x02,0x00,0x00,0xFF,0xFF,0x7F};

    set_mod_addr(com_cmd, lpa_addr);
    com_cmd[JST_CMD_OFFSET_CMD_DATA] = ul_att;
    com_cmd[JST_CMD_OFFSET_CMD_DATA + 1] = dl_att;

    *(uint16_t *)(com_cmd + JST_CMD_OFFSET_CMD_DATA + 2) = crc16_ccitt(&com_cmd[1], 5 + 2);

    debug_dump_buffer(11, com_cmd);

    nret = write(com_fd, com_cmd, 11);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            debug_dump_buffer(rsp_len, rsp_buf);
            ret = jst_msg_check(rsp_buf, rsp_len, lpa_addr, JST_CMD_SET_ATT);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", PALogError, nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t jst_set_lpa_reset(int32_t com_fd, mod_addr_t *lpa_addr)
{
    int32_t nret = 0;
    uint8_t com_cmd[10] = {0x7E,0x0E,0x00,0x88,0x00,0x01,0x00,0xFF,0xFF,0x7F};

    set_mod_addr(com_cmd, lpa_addr);

    *(uint16_t *)(com_cmd + JST_CMD_OFFSET_CMD_DATA + 1) = crc16_ccitt(&com_cmd[1], 5 + 1);

    debug_dump_buffer(10, com_cmd);

    nret = write(com_fd, com_cmd, 10);
    if( nret > 0 )
    {
        PALogNotice("write command done");
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d",nret, errno);
        return pa_err_uart_write;
    }
    return pa_ret_ok;
}


